using System.Diagnostics;

namespace Milvus.Client;

/// <summary>
/// The logical definition of a field within a <see cref="CollectionSchema" />.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class FieldSchema
{
    /// <summary>
    /// Create a field schema.
    /// </summary>
    /// <param name="name">The field name.</param>
    /// <param name="dataType">The data type stored in the field.</param>
    /// <param name="isPrimaryKey">Whether the field is a primary key.</param>
    /// <param name="autoId">Whether the field's values are automatically generated by Milvus.</param>
    /// <param name="isPartitionKey">
    /// Whether the field functions as the partition key for the collection. Available since Milvus v2.2.9.
    /// </param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema Create(
        string name,
        MilvusDataType dataType,
        bool isPrimaryKey = false,
        bool autoId = false,
        bool isPartitionKey = false,
        string description = "")
        => new(name, dataType, isPrimaryKey, autoId, isPartitionKey, description);

    /// <summary>
    /// Create a field schema.
    /// </summary>
    /// <typeparam name="TData">Determines the data type stored in the field based.</typeparam>
    /// <param name="name">The field name.</param>
    /// <param name="isPrimaryKey">Whether the field is a primary key.</param>
    /// <param name="autoId">Whether the field's values are automatically generated by Milvus.</param>
    /// <param name="isPartitionKey">
    /// Whether the field functions as the partition key for the collection. Available since Milvus v2.2.9.
    /// </param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema Create<TData>(
        string name,
        bool isPrimaryKey = false,
        bool autoId = false,
        bool isPartitionKey = false,
        string description = "")
        => new(name, FieldData.EnsureDataType<TData>(), isPrimaryKey, autoId, isPartitionKey, description);

    /// <summary>
    /// Create a field schema for a <c>varchar</c> field.
    /// </summary>
    /// <param name="name">The field name.</param>
    /// <param name="maxLength">The maximum length of the <c>varchar</c> field. Must be greater than zero.</param>
    /// <param name="isPrimaryKey">Whether the field is a primary key.</param>
    /// <param name="autoId">Whether the field's values are automatically generated by Milvus.</param>
    /// <param name="isPartitionKey">
    /// Whether the field functions as the partition key for the collection. Available since Milvus v2.2.9.
    /// </param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema CreateVarchar(
        string name,
        int maxLength,
        bool isPrimaryKey = false,
        bool autoId = false,
        bool isPartitionKey = false,
        string description = "")
        => new(name, MilvusDataType.VarChar, isPrimaryKey, autoId, isPartitionKey, description)
        {
            MaxLength = maxLength
        };

    /// <summary>
    /// Create a field schema for a float vector field.
    /// </summary>
    /// <param name="name">The field name.</param>
    /// <param name="dimension">The dimension of the vector. Must be greater than zero.</param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema CreateFloatVector(string name, int dimension, string description = "")
        => new(name, MilvusDataType.FloatVector, description: description) { Dimension = dimension };

    /// <summary>
    /// Create a field schema for a binary vector field.
    /// </summary>
    /// <param name="name">The field name.</param>
    /// <param name="dimension">The dimension of the vector. Must be greater than zero.</param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema CreateBinaryVector(string name, int dimension, string description = "")
        => new(name, MilvusDataType.BinaryVector, description: description) { Dimension = dimension };

    /// <summary>
    /// Create a field schema for a JSON field.
    /// </summary>
    /// <param name="name">The field name.</param>
    public static FieldSchema CreateJson(string name)
        => new(name, MilvusDataType.Json);

    /// <summary>
    /// Create a field schema for a <c>array</c> of <c>TData</c> field.
    /// </summary>
    /// <typeparam name="TData">Determines the element data type of array stored in the field based.</typeparam>
    /// <param name="name">The field name.</param>
    /// <param name="maxCapacity">Maximum number of elements that an array field can contain.</param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema CreateArray<TData>(
        string name,
        int maxCapacity,
        string description = "")
        => new(name, MilvusDataType.Array, description: description)
        {
            ElementDataType = FieldData.EnsureDataType<TData>(),
            MaxCapacity = maxCapacity,
        };

    /// <summary>
    /// Create a field schema for a <c>array</c> of <c>varchar</c> field.
    /// </summary>
    /// <param name="name">The field name.</param>
    /// <param name="maxCapacity">Maximum number of elements that an array field can contain.</param>
    /// <param name="maxLength">Maximum length of strings for each <c>varchar</c> element in an array field.</param>
    /// <param name="description">An optional description for the field.</param>
    public static FieldSchema CreateVarcharArray(
        string name,
        int maxCapacity,
        int maxLength,
        string description = "")
        => new(name, MilvusDataType.Array, description: description)
        {
            ElementDataType = MilvusDataType.VarChar,
            MaxCapacity = maxCapacity,
            MaxLength = maxLength,
        };

    // Construct used when the user constructs a schema to be provided to CreateSchema
    private FieldSchema(
        string name,
        MilvusDataType dataType,
        bool isPrimaryKey = false,
        bool autoId = false,
        bool isPartitionKey = false,
        string description = "")
    {
        Name = name;
        DataType = dataType;
        State = FieldState.Unknown;
        IsPrimaryKey = isPrimaryKey;
        AutoId = autoId;
        IsPartitionKey = isPartitionKey;
        Description = description;
    }

    // Constructor used when retrieving a schema via DescribeSchema
    internal FieldSchema(
        long id,
        string name,
        MilvusDataType dataType,
        MilvusDataType elementType,
        FieldState state,
        bool isPrimaryKey,
        bool autoId,
        bool isPartitionKey,
        bool isDynamic,
        string description)
    {
        FieldId = id;
        Name = name;
        DataType = dataType;
        ElementDataType = elementType;
        State = state;
        IsPrimaryKey = isPrimaryKey;
        AutoId = autoId;
        IsPartitionKey = isPartitionKey;
        IsDynamic = isDynamic;
        Description = description;
    }

    /// <summary>
    /// The field name.
    /// </summary>
    public string Name { get; }

    /// <summary>
    /// The data type stored in the field.
    /// </summary>
    public MilvusDataType DataType { get; }

    /// <summary>
    /// The element data type for array stored in the field.
    /// </summary>
    public MilvusDataType ElementDataType { get; set; }

    /// <summary>
    /// Whether the field is a primary key.
    /// </summary>
    public bool IsPrimaryKey { get; }

    /// <summary>
    /// Whether the field's values are automatically generated by Milvus.
    /// </summary>
    public bool AutoId { get; }

    /// <summary>
    /// Whether the field functions as the partition key for the collection. Available since Milvus v2.2.9.
    /// </summary>
    /// <remarks>
    /// <para>A partition key field's values are hashed and distributed to different logical partitions.</para>
    /// <para>
    /// Only fields of type <see cref="MilvusDataType.Int64" /> and <see cref="MilvusDataType.VarChar" /> type can be
    /// partition keys. The primary key field cannot be a partition key.
    /// </para>
    /// </remarks>
    public bool IsPartitionKey { get; }

    /// <summary>
    /// An optional description for the field.
    /// </summary>
    public string Description { get; }

    /// <summary>
    /// Whether this field was created dynamically. Available since Milvus v2.2.9.
    /// </summary>
    /// <remarks>
    /// <see href="https://milvus.io/docs/dynamic_schema.md" />
    /// </remarks>
    public bool IsDynamic { get; }

    /// <summary>
    /// The maximum string length. Mandatory for <see cref="MilvusDataType.VarChar" /> fields, and must be greater than
    /// zero.
    /// </summary>
    public int? MaxLength { get; set; }

    /// <summary>
    /// Maximum number of elements that an array field can contain. Mandatory for <see cref="MilvusDataType.Array" /> fields, and must be in a range [1, 4096]
    /// </summary>
    public int? MaxCapacity { get; set; }

    /// <summary>
    /// The dimension of the vector. Mandatory for <see cref="MilvusDataType.FloatVector" />
    /// and <see cref="MilvusDataType.BinaryVector" /> fields, and must be greater than zero.
    /// </summary>
    public int? Dimension { get; set; }

    /// <summary>
    /// The state of the field.
    /// </summary>
    public FieldState State { get; private set; }

    /// <summary>
    /// The internal Milvus ID assigned to this ID.
    /// </summary>
    public long FieldId { get; }

    private string DebuggerDisplay => $"{Name} ({DataType})";
}
